#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> //struct sockaddr_in
#include <arpa/inet.h>  //inet_ntoa()  inet_addr()
#include <unistd.h>     //close()
#include <cstring>
using namespace std;
/* 以下都需要包含头文件
   #include <sys/types.h>
   #include <sys/socket.h>


int socket(int domain, int type, int protocol);
    功能：为通信创建一个端点，并返回该端点对应的文件描述符，文件描述符的使用原则是最小未分配原则
    参数1：协议族，常用的协议族如下
        AF_UNIX, AF_LOCAL   本地通信，同一主机的多进程通信
        AF_INET     提供IPv4的相关通信方式
        AF_INET6      提供IPv6的相关通信方式
    参数2：通信类型，指定通信语义，常用的通信类型如下
        SOCK_STREAM   支持TCP面向连接的通信协议
        SOCK_DGRAM    支持UDP面向无连接的通信协议
    参数3：当 domain(协议族) 和 type(套接字类型) 的组合​​已经能够唯一确定​​你想要使用的协议时，protocol就可以设置为 0
    返回值：成功返回创建的端点对应的文件描述符，失败返回-1并置位错误码
*/

/*
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    功能：为套接字分配名称，给套接字绑定ip地址和端口号(客户端一般不需要绑定)
    参数1：要被绑定的套接字文件描述符
    参数2：
        通用地址信息结构体，对于不同的通信域而言，使用的实际结构体是不同的，该结构体的目的是为了强制类型转换，防止警告
            通信域为：AF_INET而言，ipv4的通信方式
            struct sockaddr_in {
            sa_family_t    sin_family; // 地址族: AF_INET
            in_port_t      sin_port;   // 端口号的网络字节序
            struct in_addr sin_addr;   // 网络地址
            };
            // Internet address.
            struct in_addr {
            uint32_t       s_addr;     // ip地址的网络字节序
            };

            通信域为：AF_UNIX而言，本地通信
            struct sockaddr_un {
            sa_family_t sun_family;               // 通信域：AF_UNIX
            char sun_path[UNIX_PATH_MAX];  // 通信使用的文件
            };
    参数3：参数2的大小
    返回值：成功返回0，失败返回-1并置位错误码
*/

/*
int listen(int sockfd, int backlog);
    功能：将套接字设置成被动监听状态
    参数1：套接字文件描述符
    参数2：挂起队列能够增长的最大长度，一般为128(控制的是“正在排队等待建立连接或等待被accept()接收”的连接数量上限)
    返回值：成功返回0，失败返回-1并置位错误码
*/

/*
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    功能：阻塞等待客户端的连接请求，如果已连接队列中有客户端，则从连接队列中拿取第一个，并创建一个用于通信的套接字
    参数1：服务器套接字文件描述符
    参数2：通用地址信息结构体，用于接受已连接的客户端套接字地址信息的(不想接受可以NULL,参数三也要是NULL)
    参数3：接收参数2的大小
    返回值：成功返回一个新的用于通信的套接字文件描述符，失败返回-1并置位错误码
*/

/*
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    功能：从套接字中读取消息放入到buf中
    参数1：通信的套接字文件描述符
    参数2：要存放数据的起始地址
    参数3：读取的数据的大小
    参数4：读取标识位，是否阻塞读取
            0：表示阻塞等待
            MSG_DONTWAIT：非阻塞
    返回值：可以是大于0：表示成功读取的字节个数
            可以是等于0：表示对端已经下线（针对于TCP通信）
            -1：失败，并置位错误码
*/

/*
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
    功能：向套接字文件描述符中将buf这个容器中的内容写入
    参数1：通信的套接字文件描述符
    参数2：要发送的数据的起始地址
    参数3：发送的数据的大小
    参数4：读取标识位，是否阻塞读取
            0：表示阻塞等待
            MSG_DONTWAIT：非阻塞
    返回值：成功返回发送字节的个数
            -1：失败，并置位错误码
*/

/*
#include <unistd.h>

    int close(int fd);
    功能：关闭套接字文件描述符
    参数：要关闭的套接字文件描述符
    返回值：成功返回0，失败返回-1并置位错误码
*/
#define SET_SERVER_PORT 9090
#define SET_SERVER_IP "192.168.184.129"
int main()
{
    // 1、创建用于连接的套接字文件描述符(文件描述符File Descriptor，简称fd)
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    // 参数1：AF_INET表示使用的是ipv4的通信协议
    // 参数2：SOCK_STREAM表示使用的是tcp通信
    // 参数3：由于参数2指定了协议，参数3填0即可
    if (sfd == -1)
    {
        perror("socket error");
        return -1;
    }
    printf("socket success sfd = %d\n", sfd);

    // 2、绑定ip地址和端口号
    // 2.1 填充要绑定的ip地址和端口号结构体
    struct sockaddr_in server_in;                         // 定义服务器地址结构体
    server_in.sin_family = AF_INET;                       // 地址族为IPv4
    server_in.sin_port = htons(SET_SERVER_PORT);          // 服务器端口号
    server_in.sin_addr.s_addr = inet_addr(SET_SERVER_IP); // 服务器IP地址
    // 2.2 绑定工作
    // 参数1：要被绑定的套接字文件描述符
    // 参数2：要绑定的地址信息结构体，需要进行强制类型转换，防止警告
    // 参数3：参数2的大小
    if (bind(sfd, (struct sockaddr *)&server_in, sizeof(server_in)) == -1)
    {
        perror("bind error");
        return -1;
    }
    printf("bind success\n");

    // 3、启动监听
    // 参数1：要启动监听的文件描述符
    // 参数2：挂起队列的长度
    if (listen(sfd, 128) == -1)
    {
        perror("listen error");
        return -1;
    }
    printf("listen success\n");

    // 4、阻塞等待客户端的连接请求
    // 定义变量，用于接受客户端地址信息结构体
    struct sockaddr_in client_in;          // 用于接收地址信息结构体的
    socklen_t socklen = sizeof(client_in); // 用于接收地址信息的长度
    int acceptfd = accept(sfd, (struct sockaddr *)&client_in, &socklen);
    // 参数1：服务器套接字文件描述符
    // 参数2：用于接收客户端地址信息结构体的容器，如果不接收，也可以填NULL
    // 参数3：接收参数2的大小，如果参数2为NULL，则参数3也是NULL
    if (acceptfd == -1)
    {
        perror("accept error");
        return -1;
    }
    printf("[%s:%d]:已连接成功!!!!\n", inet_ntoa(client_in.sin_addr), ntohs(client_in.sin_port));

    // 5、数据收发
    char rbuf[128] = ""; // 数据容器
    while (1)
    {
        // 清空容器中的内容(防止数据残留，当然人为设置\0也可以，不过后面数据残留还在，建议清理)
        bzero(rbuf, sizeof(rbuf)); // 不是c/c++的函数，是linux的函数，跟memset一样功能
        // 从套接字中读取消息
        int res = recv(acceptfd, rbuf, sizeof(rbuf), 0);
        if (res == -1)
        {
            perror("recv error");
            return -1;
        }
        else if (res == 0)
        {
            printf("客户端已经下线\n");
            break;
        }
        printf("[%s:%d]:%s\n", inet_ntoa(client_in.sin_addr), ntohs(client_in.sin_port), rbuf);
        // 对收到的数据处理一下，回给客户端
        strcat(rbuf, "*_*");
        // 将消息发送给客户端
        if (send(acceptfd, rbuf, strlen(rbuf), 0) == -1)
        {
            perror("send error");
            return -1;
        }
        printf("发送成功\n");
    }

    // 6、关闭套接字
    close(acceptfd);
    close(sfd);

    std::cout << "服务器关闭" << std::endl;
    return 0;
}
